home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 5
/
Apprentice-Release5.iso
/
Source Code
/
C
/
Frameworks
/
Grant's CGI Framework 1.0b14
/
Util
/
ProcessUtil.c
< prev
next >
Wrap
Text File
|
1996-04-12
|
19KB
|
740 lines
/*****
*
* ProcessUtil.c
*
* This is a support file for "Grant's CGI Framework".
* Please see the license agreement that accompanies the distribution package
* for licensing details.
*
* Copyright ©1995,1996 by Grant Neufeld
* grant@acm.com
* http://arpp.carleton.ca/grant/
*
*****/
#include "MyConfiguration.h"
#include <Threads.h>
#include "compiler_stuff.h"
#include "globals.h"
#include "DebugUtil.h"
#include "ProcessUtil.h"
/*** LOCAL VARIABLES ***/
static short vAppBusyLevel; /* how busy the application is */
static UInt32 vSleepTicksDefault; /* sleep time when app is idle */
static UInt32 vSleepTicksBusy; /* sleep time when app is processing */
static ThreadID vThreadMain; /* main application thread id */
static Boolean vThreadMainIsAsleep;
#if kCompileWithDeferredTask
static ThreadID vDeferredTaskThread;
#endif
//static ThreadID vThreadStatusUpdate;
/* an array of sleeping thread IDs */
#if kStartupThreadsPreallocate > nil
static ThreadID vThreadSleepers[kStartupThreadsPreallocate]; /* array of sleepers */
static ThreadID vThreadSleeperHead; /* next thread to wake up */
static ThreadID vThreadSleeperAddHere; /* next open spot in queue */
static short vThreadSleepersTotal;
#endif
/*** LOCAL PROTOTYPES ***/
pascal void threadTermination ( ThreadID, void * );
pascal void * threadDeferredTaskThread ( void * );
static void threadSleepersInit ( void );
/*** FUNCTIONS ***/
/* */
void
ProcessStartup ( void )
{
ProcessSleepSetDefault ( kSleepTicks );
ProcessSleepSetBusy ( kSleepTicksWhenBusy );
ProcessSleepDefault ();
/* we're not busy yet */
vAppBusyLevel = nil;
gFrontProcess = ProcessCurrentIsFront ();
#if kCompileWithProcessFileSpec
ProcessGetMyFSSpec ( &gProcessFSSpec );
#endif
} /* ProcessStartup */
/* Give time for other processes (both internal to the app and other applications) */
p_export
void
ProcessGiveTime ( UInt32 sleepTicks )
{
OSErr theErr;
#if kCompileWithThreadsOptional
EventRecord theEvent;
if ( gHasThreadMgr )
{
#endif
theErr = ThreadYield ( nil, true );
#if kCompileWithThreadsOptional
}
else
{
if ( sleepTicks > nil )
{
/* if sleepTicks is a 'usable' value, use it */
WaitNextEvent ( nil, &theEvent, sleepTicks, NULL );
}
else
{
/* otherwise, use the current application sleep ticks setting */
WaitNextEvent ( nil, &theEvent, gSleepTicks, NULL );
}
}
#endif
} /* ProcessGiveTime */
/* Determine if the current application is active (is in front of all others) */
Boolean
ProcessCurrentIsFront ( void )
{
#if kCompileWithForeground
Boolean isFront;
ProcessSerialNumber myPSN;
OSErr theErr;
/* get my process serial number. IM:Processes 2-21 */
theErr = GetCurrentProcess ( &myPSN );
if ( theErr != noErr )
{
/* might want to do something else here to handle the error */
return false;
}
isFront = ProcessIsFront ( &myPSN );
return isFront;
#else /* if not kCompileWithForeground */
return false;
#endif /* kCompileWithForeground */
} /* ProcessCurrentIsFront */
/* Run through WaitNextEvent, not handling events, until the process comes
to the foreground.
Returns false if timeout is reached before process brought to front.
Timeout is in ticks (60ths of a second). If timeout is nil - doesn't timeout. */
Boolean
ProcessWaitUntilFront ( unsigned long timeout )
{
#if kCompileWithForeground
Boolean isFront;
long endTime;
long sleepTicks;
isFront = ProcessCurrentIsFront ();
if ( !isFront )
{
endTime = TickCount() + timeout;
if ( (timeout > 20) || (timeout == nil) )
{
sleepTicks = 20;
}
else
{
sleepTicks = timeout;
}
do
{
ProcessGiveTime ( sleepTicks );
isFront = ProcessCurrentIsFront ();
} while ( !isFront && ((timeout == nil) || (endTime >= TickCount())) );
}
return isFront;
#else
/* can't be in foreground/front if application is background only */
return false;
#endif /* kCompileWithForeground */
} /* ProcessWaitUntilFront */
/* Determine if the specified application is active (is in front of all others) */
Boolean
ProcessIsFront ( ProcessSerialNumber *thePSN )
{
#if kCompileWithForeground
OSErr theErr;
ProcessSerialNumber frontPSN;
my_assert ( thePSN != NULL, "\pProcessIsFront: thePSN ptr is NULL" );
theErr = GetFrontProcess ( &frontPSN );
if ( theErr != noErr )
{
/* might want to do something else here to handle the error */
return false;
}
return ( (frontPSN.lowLongOfPSN == thePSN->lowLongOfPSN) &&
(frontPSN.highLongOfPSN == thePSN->highLongOfPSN) );
#else
/* can't be in foreground/front if application is background only */
return false;
#endif /* kCompileWithForeground */
} /* ProcessIsFront */
/* Fill out an already allocated FSSpec with the current process' location.
Thanks to Gregory S. Combs for providing most of this function. */
OSErr
ProcessGetMyFSSpec ( FSSpec *procFileSpec )
{
OSErr theErr;
ProcessSerialNumber curPSN;
ProcessInfoRec procInfo;
Str255 appName;
my_assert ( procFileSpec != NULL, "\pProcessGetMyFSSpec: procFileSpec ptr is NULL" );
theErr = GetCurrentProcess ( &curPSN );
if ( theErr == noErr )
{
procInfo.processInfoLength = sizeof ( ProcessInfoRec );
procInfo.processName = appName;
procInfo.processAppSpec = procFileSpec;
theErr = GetProcessInformation ( &curPSN, &procInfo );
}
return theErr;
} /* ProcessGetMyFSSpec */
/** BUSY LEVEL and SLEEP TICKS **/
#pragma mark -
/* The 'busy' level of the application affects how much time it will try to
grab for processing */
/* */
p_export
void
ProcessIsMoreBusy ( void )
{
OSErr theErr;
if ( vAppBusyLevel == nil )
{
/* we weren't busy, so need to move into busy state */
/* adjust the sleepTicks */
ProcessSleepBusy ();
/* put the deferredTasks thread to sleep */
#if kCompileWithDeferredTask
if ( vDeferredTaskThread != nil )
{
theErr = SetThreadState ( vDeferredTaskThread, kStoppedThreadState, nil );
}
#endif
}
vAppBusyLevel++;
} /* ProcessIsMoreBusy */
/* */
p_export
void
ProcessIsLessBusy ( void )
{
OSErr theErr;
vAppBusyLevel--;
if ( vAppBusyLevel == nil )
{
/* we're no longer busy, so need to move into non-busy state */
/* adjust the sleepTicks */
ProcessSleepDefault ();
/* wake up the deferredTasks thread */
#if kCompileWithDeferredTask
if ( vDeferredTaskThread != nil )
{
theErr = SetThreadState ( vDeferredTaskThread, kReadyThreadState, nil );
}
#endif
}
} /* ProcessIsLessBusy */
/* sleepTicks is used to determine how much time to give for other processes
to use. */
/* set the number of ticks used for normal sleep time */
p_export
void
ProcessSleepSetDefault ( UInt32 sleepTicks )
{
vSleepTicksDefault = sleepTicks;
} /* ProcessSleepSetDefault */
/* set the number of ticks used for busy sleep time */
p_export
void
ProcessSleepSetBusy ( UInt32 sleepTicks )
{
vSleepTicksBusy = sleepTicks;
} /* ProcessSleepSetBusy */
/* Call this when app becmoes not busy */
p_export
void
ProcessSleepDefault ( void )
{
gSleepTicks = vSleepTicksDefault;
} /* ProcessSleepDefault */
/* Call this when app becmoes busy */
p_export
void
ProcessSleepBusy ( void )
{
gSleepTicks = vSleepTicksBusy;
} /* ProcessSleepBusy */
/** THREAD FUNCTIONS **/
#pragma mark -
void
ThreadStartup ( void )
{
OSErr theErr;
long feature;
#if kCompileWithThreadsOptional
gHasThreadMgr = false;
#endif
theErr = Gestalt ( gestaltThreadMgrAttr, &feature );
if ( theErr == noErr )
{
if (
#ifdef powerc
( feature & (1 << gestaltThreadsLibraryPresent) ) &&
/* ••• Can anybody who does PowerPC code tell me why the following
line doesn't work?
( (Ptr)NewThread != kUnresolvedSymbolAddress ) &&
••• */
#endif /* def powerc */
/* the feature check applies to both 68K & PPC */
( feature & (1 << gestaltThreadMgrPresent) ) )
{
#if kCompileWithThreadsOptional
gHasThreadMgr = true;
#endif
gThreadQuit = false;
/* no sub threads running yet */
// #if kStartupThreadsPreallocate > nil
gThreadTotal = nil;
// /* no sleeping thread yet */
// gThreadSleeperIndex = 0;
// gThreadSleeper = nil;
// #endif
/* determine the id of the main (current) thread */
GetCurrentThread ( &vThreadMain );
vThreadMainIsAsleep = false;
#if (kStartupThreadsPreallocate > nil)
/* pre-allocate the required number of threads */
CreateThreadPool ( kCooperativeThread, kStartupThreadsPreallocate, nil );
threadSleepersInit ();
#endif
/* create the deferred tasks thread */
#if kCompileWithDeferredTask
theErr = ThreadNewThreadFromPool ( threadDeferredTaskThread,
(void *)NULL, (void**)NULL, &vDeferredTaskThread );
#endif
}
}
} /* ThreadStartup */
/* you should always make sure that your sub-threads can't get 'stuck' in a loop.
They should, at the very least, check gThreadQuit periodically so that the
application can quit without getting hung on threads that won't go away. */
/* Allocate a new thread from the existing pool of threads.
If there are no threads available, yield to other threads until one finishes.
The Thread Manager must be available for this function to work. */
#if kStartupThreadsPreallocate > nil
p_export
OSErr
ThreadNewThreadFromPool (
ThreadEntryProcPtr threadEntry,
void * threadParam,
void ** threadResult,
ThreadID * threadMade )
{
OSErr theErr;
short threadsFree;
ThreadID currentThread;
#if kCompileWithThreadsOptional
my_assert ( gHasThreadMgr, "\pThreadNewThreadFromPool: Thread Manager not available" );
#endif
theErr = GetFreeThreadCount ( kCooperativeThread, &threadsFree );
if ( theErr == noErr )
{
theErr = GetCurrentThread ( ¤tThread );
}
if ( (theErr == noErr) && (threadsFree == nil) )
{
/* Put the current thread to sleep, to be woken up when a thread
becomes available. */
theErr = ThreadSleep ( currentThread );
}
if ( theErr == noErr )
{
/* Install the new thread using a premade thread from the pool. */
theErr = NewThread ( kCooperativeThread, threadEntry, threadParam, nil,
kFPUNotNeeded + kUsePremadeThread, threadResult, threadMade );
}
if ( theErr == noErr )
{
/* set the termination function for the thread. */
SetThreadTerminator ( *threadMade, threadTermination, NULL );
/* increment the total number of sub-threads. */
++gThreadTotal;
}
return theErr;
} /* ThreadNewThreadFromPool */
#endif
/* The Thread Manager must be available for this function to work. */
#if kStartupThreadsPreallocate > nil
pascal void
threadTermination ( ThreadID threadTerminated, void *terminationProcParam )
{
OSErr theErr;
#if kCompileWithThreadsOptional
my_assert ( gHasThreadMgr, "\pmyThreadTermination: Thread Manager not available" );
#endif
my_assert ( gThreadTotal > nil, "\pmyThreadTermination: gThreadTotal is too small" );
/* if there are sleeping threads, wake one up since this one will be disposed */
theErr = ThreadWakeNext ();
/* Lower the count of sub-threads. */
--gThreadTotal;
} /* threadTermination */
#endif
/* Call this instead of YieldToAnyThread.
suggestThread is the suggested (but not required) thread to yield to next. */
p_export
OSErr
ThreadYield ( ThreadID suggestThread, Boolean useWNE )
{
OSErr theErr;
#if kStartupThreadsPreallocate > nil
EventRecord theEvent;
#endif
#if kCompileWithThreadsOptional
my_assert ( gHasThreadMgr, "\pThreadYield: Thread Manager not available" );
#endif
theErr = YieldToAnyThread ();
/* if there's a sleeping thread, it's probably the main thread, so we'll
need to give time to other apps to process */
#if kStartupThreadsPreallocate > nil
if ( useWNE && (vThreadSleepersTotal > nil) )
{
WaitNextEvent ( nil, &theEvent, gSleepTicks, NULL );
}
#endif
return theErr;
} /* ThreadYield */
/* Give all sub-threads a chance to finish */
#if (kStartupThreadsPreallocate > nil)
void
ThreadFinishAllSubThreads ( void )
{
OSErr theErr;
ThreadID currentThread;
#if kCompileWithThreadsOptional
my_assert ( gHasThreadMgr, "\pThreadFinishAllSubThreads: Thread Manager not available" );
#endif
theErr = GetCurrentThread ( ¤tThread );
if ( currentThread != vThreadMain )
{
/* lower the count of sub-threads to prevent this sub-thread from
preventing the exit of the while loop below */
--gThreadTotal;
}
gThreadQuit = true;
while ( gThreadTotal > nil )
{
if ( gThreadTotal <= vThreadSleepersTotal )
{
/* remaining threads are sleeping, so wake one up */
ThreadWakeNext ();
}
/* while there remain other threads (not including the current and main),
let them finish before quitting */
ProcessGiveTime ( nil );
}
if ( currentThread != vThreadMain )
{
/* increase the count of sub-threads */
++gThreadTotal;
}
} /* ThreadFinishAllSubThreads */
#endif
/** Deferred Tasks (low-priority) thread **/
#pragma mark -
#if kCompileWithDeferredTask
/* */
pascal void *
threadDeferredTaskThread ( void *threadParam )
{
ThreadID currentThread;
#if kCompileWithThreadsOptional
my_assert ( gHasThreadMgr, "\pthreadDeferredTaskThread: Thread Manager not available" );
#endif
/* continually call CustomDeferredTask until it is time to quit */
while ( !gThreadQuit )
{
CustomDeferredTask ();
ProcessGiveTime ( nil );
}
/* Find the ID of current thread and use DisposeThread to dispose of it so
that my custom thread termination procedure will be used to recycle
this thread's allocation for the thread pool. */
GetCurrentThread ( ¤tThread );
DisposeThread ( currentThread, (void *)noErr, true );
/* This line below is actually irrelevant, since the DisposeThread call above
will result in the immediate termination of this thread.
I keep it in because a return result is needed for the compiler not to
issue a warning (and I have the "treat all warnings as errors" flag set
in my compiler, like every programmer should). */
return (void *)noErr;
} /* threadDeferredTaskThread */
#endif /* kCompileWithDeferredTask */
/** SLEEPING THREADS **/
#pragma mark -
#if kStartupThreadsPreallocate > nil
/* this section takes care of dealing with putting threads to sleep, waking
them up, and keeping track of the sleeping threads.
It only really applies to situations when resources are tied up and a
thread can't proceed until some resources become available. In particular,
when allocating new threads from the pool of preallocated threads, if the
pool contains no unused threads, the thread attempting to allocate can go
to sleep until some other thread quits resulting in a thread becoming
available.
If you want to be able to put a specific thread to sleep and later wake it
up under specific circumstances (where you don't want to rely on the queue
to wake it up because it might be woken too early or too late), you will
have to handle the sleeping and waking on your own (don't touch the
sleeping threads queue implemented in this module.
I've implemented a queue (vThreadSleepers) to track the sleeping threads;
it stores the ThreadIDs which can then be used to wake up the threads on
a FIFO (first in first out) basis.
The position for where to add threads to the queue (as they are put to
sleep) is tracked in vThreadSleeperAddHere.
The position for where the next thread to wake is in the queue is
tracked in vThreadSleeperHead.
The first item in the vThreadSleepers array is identified as 0 (zero),
and the last is identified by kStartupThreadsPreallocate - 1. */
/* */
static void
threadSleepersInit ( void )
{
short counter;
#if kCompileWithThreadsOptional
my_assert ( gHasThreadMgr, "\pthreadSleepersInit: Thread Manager not available" );
#endif
for ( counter = 0; counter < kStartupThreadsPreallocate; counter++ )
{
vThreadSleepers[counter] = nil;
}
vThreadSleeperHead = nil;
vThreadSleeperAddHere = nil;
vThreadSleepersTotal = nil;
} /* threadSleepersInit */
/* */
p_export OSErr
ThreadSleep ( ThreadID theThread )
{
OSErr theErr;
#if kCompileWithThreadsOptional
my_assert ( gHasThreadMgr, "\pThreadSleep: Thread Manager not available" );
#endif
my_assert ( theThread != nil, "\pThreadSleep: theThread is nil" );
if ( vThreadSleepersTotal == kStartupThreadsPreallocate )
{
/* cannot put all of the application threads to sleep, at least one
must be running! */
theErr = true; /* ••• this should be a more meaningful error value */
}
else
{
my_assert ( vThreadSleepers[vThreadSleeperAddHere] == nil,
"\pThreadSleep: the AddHere spot in the vThreadSleepers queue is not nil" );
if ( theThread == vThreadMain )
{
/* we're putting the main thread to sleep */
vThreadMainIsAsleep = true;
}
/* add the threadID to the queue */
vThreadSleepers[vThreadSleeperAddHere] = theThread;
/* adjust the index to the next open spot in the queue */
++vThreadSleeperAddHere;
if ( vThreadSleeperAddHere >= kStartupThreadsPreallocate )
{
vThreadSleeperAddHere = 0;
}
/* we've got one more sleeping thread, now */
++vThreadSleepersTotal;
/* this is the call to put the thread to sleep */
theErr = SetThreadState ( theThread, kStoppedThreadState, nil );
}
return theErr;
} /* ThreadSleep */
/* Waking up a thread does not mean that it will immediately start processing,
it just means that the thread will be back in the set of threads that can
be yielded to when ThreadYield is called. */
p_export
OSErr
ThreadWakeNext ( void )
{
OSErr theErr;
ThreadID theThread;
#if kCompileWithThreadsOptional
my_assert ( gHasThreadMgr, "\pThreadWakeNext: Thread Manager not available" );
#endif
/* extract the threadID from the queue */
theThread = vThreadSleepers[vThreadSleeperHead];
if ( theThread == nil )
{
/* no sleeping threads */
theErr = true; /* ••• need better error value here */
}
else
{
if ( theThread == vThreadMain )
{
/* we're waking up the main thread */
vThreadMainIsAsleep = false;
}
/* adjust the index of the head of the queue to the next in line */
vThreadSleepers[vThreadSleeperHead] = nil;
++vThreadSleeperHead;
if ( vThreadSleeperHead >= kStartupThreadsPreallocate )
{
vThreadSleeperHead = nil;
}
/* we've got one less sleeping thread, now */
--vThreadSleepersTotal;
/* this is the call to wake up the thread */
theErr = SetThreadState ( theThread, kReadyThreadState, nil );
}
return theErr;
} /* ThreadWakeNext */
/* */
void
ThreadWakeAll ( ThreadID theThread )
{
#if kCompileWithThreadsOptional
my_assert ( gHasThreadMgr, "\pThreadWakeAll: Thread Manager not available" );
#endif
while ( vThreadSleepersTotal > nil )
{
ThreadWakeNext ();
}
} /* ThreadWakeAll */
#endif /* kStartupThreadsPreallocate > nil */
/***** EOF *****/